using System;
using System.Collections;
using System.IO;
using System.Collections.Generic;

namespace System.Windows.Media.DirectShow.Ogg
{

    public interface IPhysicalOggStream
    {
        ICollection LogicalStreams
        {
            get;
        }
        OggPage GetOggPage(int index);
        bool Open
        {
            get;
        }
        void Close();
        long Time
        {
            set;
        }
    }

    public interface ILogicalOggStream
    {
        byte[] GetNextOggPacket();
        long MaximumGranulePosition
        {
            get;
        }
        void Reset();
        bool Open
        {
            get;
        }
        void Close();
        long Time
        {
            set;
        }
    }


    public class OggFileStream : IPhysicalOggStream
    {

        private bool closed = false;
        private Stream source;
        private long[] pageOffsets;
        //private long numberOfSamples=-1;

        private Dictionary<int, LogicalOggStream> logicalStreams = new Dictionary<int, LogicalOggStream>();


        public OggFileStream(Stream source)
        {
            this.source = source;

            List<Object> po = new List<Object>();
            int pageNumber = 0;

            while (true)
            {
                po.Add(this.source.Position);

                OggPage op = GetNextPage(true);
                if (op == null) break;

                LogicalOggStream los = null;
                if (!logicalStreams.TryGetValue(op.StreamSerialNumber, out los))
                {
                    los = new LogicalOggStream(this, op.StreamSerialNumber);
                    logicalStreams.Add(op.StreamSerialNumber, los);
                }

                los.AddPageNumberMapping(pageNumber);
                los.AddGranulePosition(op.AbsoluteGranulePosition);

                this.source.Position += op.TotalLength;
                pageNumber++;
            }

            this.source.Position = 0;
            pageOffsets = new long[po.Count];
            int i = 0;
            foreach (long poff in po)
            {
                pageOffsets[i++] = poff;
            }
        }

        public ICollection LogicalStreams
        {
            get
            {
                return logicalStreams.Values;
            }
        }

        public bool Open
        {
            get
            {
                return !closed;
            }
        }

        public void Close()
        {
            closed = true;
            source.Close();
        }

        private OggPage GetNextPage()
        {
            return GetNextPage(false);
        }

        private OggPage GetNextPage(bool skipData)
        {
            return OggPage.Create(source, skipData);
        }

        public OggPage GetOggPage(int index)
        {
            source.Position = pageOffsets[index];
            return OggPage.Create(source);
        }

        public long Time
        {
            set
            {
                foreach (LogicalOggStream los in logicalStreams.Values)
                {
                    los.Time = value;
                }
            }
        }

    }

    public class LogicalOggStream : ILogicalOggStream
    {

        private IPhysicalOggStream source;
        private int serialNumber;

        private List<Int32> pageNumberMapping = new List<Int32>();
        private List<long> granulePositions = new List<long>();

        private int pageIndex = 0;
        private OggPage currentPage;
        private int currentSegmentIndex;

        private bool open = true;

        public LogicalOggStream(IPhysicalOggStream source, int serialNumber)
        {
            this.source = source;
            this.serialNumber = serialNumber;
        }

        public void AddPageNumberMapping(int physicalPageNumber)
        {
            pageNumberMapping.Add(physicalPageNumber);
        }

        public void AddGranulePosition(long granulePosition)
        {
            granulePositions.Add(granulePosition);
        }

        public void Reset()
        {
            this.Reset(2);
        }

        public void Reset(int pageIndex)
        {
            currentPage = null;
            currentSegmentIndex = 0;
            this.pageIndex = pageIndex;
        }

        public byte[] GetNextOggPacket()
        {
            MemoryStream res = new MemoryStream();
            int segmentLength = 0;
            if (currentPage == null)
            {
                currentPage = source.GetOggPage((int)pageNumberMapping[pageIndex++]);
            }
            do
            {
                if (currentSegmentIndex >= currentPage.SegmentOffsets.Length)
                {
                    OggPage op;
                    if (pageIndex >= pageNumberMapping.Count)
                    {
                        op = source.GetOggPage(pageNumberMapping[pageNumberMapping.Count - 1] + 1);
                    }
                    else
                        op = source.GetOggPage((int)pageNumberMapping[pageIndex]);
                    if (op == null)
                        return null;

                    currentSegmentIndex = 0;
                    currentPage = op;
                    pageIndex++;
                }
                segmentLength = currentPage.SegmentLengths[currentSegmentIndex];
                res.Write(currentPage.Data, currentPage.SegmentOffsets[currentSegmentIndex], segmentLength);
                currentSegmentIndex++;
            } while (segmentLength == 255);
            return res.ToArray();
        }

        public bool Open
        {
            get
            {
                return open;
            }
        }

        public void Close()
        {
            open = false;
        }

        public long MaximumGranulePosition
        {
            get
            {
                return (long)granulePositions[granulePositions.Count - 1];
            }
        }

        public long Time
        {
            set
            {
                int page = 0;
                for (page = 0; page < granulePositions.Count; page++)
                {
                    long gp = (long)granulePositions[page];
                    if (gp > value)
                    {
                        break;
                    }
                }

                pageIndex = page;
                currentPage = source.GetOggPage((int)pageNumberMapping[pageIndex++]);
                currentSegmentIndex = 0;
                int segmentLength = 0;
                do
                {
                    if (currentSegmentIndex >= currentPage.SegmentOffsets.Length)
                    {
                        currentSegmentIndex = 0;
                        if (pageIndex >= pageNumberMapping.Count)
                        {
                            throw new EndOfStreamException();
                        }
                        currentPage = source.GetOggPage((int)pageNumberMapping[pageIndex++]);
                    }
                    segmentLength = currentPage.SegmentLengths[currentSegmentIndex];
                    currentSegmentIndex++;
                } while (segmentLength == 255);
            }
        }

    }
}